package com.example.tinker;

import android.content.Context;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * Classloader 和 Application  的生命周期是一致的
 * 如果我有多个修复包   那是不是每次重启后都要进行每个修复包的修复
 */
public class FixManager {

    //  存储所有修复包的容器
    private static HashSet<File> loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    public static void loadDex(Context context) {

        if (context == null) {
            return;
        }

        //  第一步获取到所有的修复包、补丁
        File odex = context.getDir("out.dex", Context.MODE_PRIVATE);
        // 获取到odex 下面所有的补丁
        File[] files = odex.listFiles();
        // 为了确保所有的子文件都是补丁，进行验证
        for (File file : files) {
            //如果所有补丁后缀名都是   dex  的
            if (file.getName().endsWith(".dex")) {
                loadedDex.add(file);
            }
        }

        //  创建一个缓存目录 （解压后的文件在存储目录）
        String optimizeDir = odex.getAbsolutePath() + File.separator + "odex";
        File fpot = new File(optimizeDir);
        if (!fpot.exists()) {//   不为空
            fpot.mkdirs();//  删除掉
        }

        //  第二步  修复所有bug
        for (File dex : loadedDex) {
            try {
                //要讲补丁的dexElements  和 当前应用的 dexElements  进行合并
                // 然后将合并的数组赋值给当前应用的类加载器
                //首先，要获取到当前应用的类加载器
                PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
                // 获取到 pathClassLoader 的父类 BaseDexClassLoader
                Class<?> superclass = pathClassLoader.getClass().getSuperclass();
                // 获取到 BaseDexClassLoader 里面的一个叫做 pathList 的成员变量
                Field pathListField = superclass.getDeclaredField("pathList");
                // 设置可以被访问
                pathListField.setAccessible(true);
                //获取到 pathList 在当前应用的类加载器中的值
                Object pathListValue = pathListField.get(pathClassLoader);
                //  获取到 dexElements 的成员变量
                Field dexElementsField = pathListValue.getClass()
                        .getDeclaredField("dexElements");
                dexElementsField.setAccessible(true);
                //获取 dexElements 这个变量在类加载器中的（pathList）的值
                Object dexElementsValue = dexElementsField.get(pathListValue);

                // 接下来，去获取 补丁的dexElements
                // 先用一个类加载器去加载补丁
                DexClassLoader dexClassLoader = new DexClassLoader(dex.getAbsolutePath(),
                        fpot.getAbsolutePath(), null, context.getClassLoader());
                // 补丁的类加载器里面的 pathList 的值
                Object dexPathListValue = pathListField.get(dexClassLoader);
                //  补丁的类加载器里面的 dexElements 的值
                Object dexDexFlementsValue = dexElementsField.get(dexPathListValue);

                //  然后进行合并
                int length = Array.getLength(dexElementsValue);
                int myLength = Array.getLength(dexDexFlementsValue);
                int newLength = myLength + length;
                //  创建一个新的数组
                //  首先获取到数组的类型
                Class<?> componentType = dexDexFlementsValue.getClass().getComponentType();
                Object newElementsArray = Array.newInstance(componentType, newLength);
                for (int i = 0; i < newLength; i++) {
                    // 注意事项  要将补丁的数据放到数组的前面
                    if (i < myLength) {
                        Array.set(newElementsArray, i, Array.get(dexDexFlementsValue, i));
                    } else {
                        Array.set(newElementsArray, i, Array.get(dexElementsValue, i - myLength));
                    }
                }
                // 将新数组的值赋值给当前应用的类加载器
                dexElementsField.set(pathListValue, newElementsArray);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    }


}
